package com.imcrl.component.drager{
	
	import com.imcrl.core.StarlingHelper;
	import com.imcrl.event.DragAndDropEvent;
	
	import flash.geom.*;
	
	import starling.core.Starling;
	import starling.display.DisplayObject;
	import starling.display.DisplayObjectContainer;
	import starling.display.Sprite;
	import starling.display.Stage;
	import starling.events.Touch;
	import starling.events.TouchEvent;
	import starling.events.TouchPhase;
	
	/**
	 * 拖拽管理类; 
	 * @author crl
	 * 
	 */	
	public class DragManager{
		
		public static const NONE:String = "none";
		public static const MOVE:String = "move";
		public static const COPY:String = "copy";
		public static const LINK:String="link";
		
		public static var DEFAULT_DROP_MOTION:IDropMotion = new DropMotion();
		public static var DEFAULT_REJECT_DROP_MOTION:IDropMotion = new RejectedMotion();
		public static var DEFAULT_DRAG_IMAGE:IDraggingImage=new DefaultDragImage();
		
		private static var _isDragging:Boolean = false;
		
		private static var _lastMouseEvent:TouchEvent;
		private static var _dragListener:IDragListener;
		private static var _dragInitiator:AbstractDragger;
		private static var _sourceData:*;
		private static var _dragImage:IDraggingImage;
		public static var action:String;
		
		private static var root:DisplayObjectContainer = null;
		private static var dropMotion:IDropMotion;
		private static var runningMotion:IDropMotion;
		private static var dragProxyMC:Sprite;
		private static var mouseOffset:Point;
		private static var target:AbstractDragger;
		
		private static var listeners:Array = new Array();
		private static var curStage:Stage;
		
		/**
		 * Sets the container to hold the draging image(in fact it will hold the image's parent--a sprite).
		 * By default(if you have not set one), it will be the <code>AsWingManager.getRoot()</code> value.
		 * @param theRoot the container to hold the draging image.
		 * @see org.aswing.AsWingManager#getRoot()
		 */
		public static function setDragingImageContainerRoot(theRoot:DisplayObjectContainer):void{
			root = theRoot;
		}
		
		/**
		 * <pre>
		 * startDrag(dragInitiator:Component, dragSource:SourceData, dragImage:MovieClip, lis:DragListener)
		 * startDrag(dragInitiator:Component, dragSource:SourceData, dragImage:MovieClip)
		 * startDrag(dragInitiator:Component, dragSource:SourceData)
		 * </pre>
		 * <p>
		 * Starts dragging a initiator, with dragSource, a dragging Image, and a listener. The drag action will be finished
		 * at next Mouse UP/Down Event(Mouse UP or Mouse Down, generally you start a drag when mouse down, then it will be finished
		 * when mouse up, if you start a drag when mouse up, it will be finished when mouse down).
		 * </p>
		 * @param dragInitiator the dragging initiator
		 * @param sourceData the data source will pass to the listeners and target components
		 * @param dragImage (optional)the image to drag, default is a rectangle image.
		 * @param dragListener (optional)the listener added to just for this time dragging action, default is null(no listener)
		 */
		public static function startDrag(dragInitiator:AbstractDragger, sourceData:*,event:TouchEvent ,dragImage:IDraggingImage=null, dragListener:IDragListener=null):void{
			if(_isDragging){
				throw new Error("The last dragging action is not finished, can't start a new one!");
				return;
			}
			var stage:Stage = dragInitiator.stage;
			if(stage == null){
				throw new Error("The drag initiator is not on stage!");
				return;
			}
			curStage = stage;
			if(dragImage == null){
				dragImage = DEFAULT_DRAG_IMAGE;
				DEFAULT_DRAG_IMAGE.data=dragInitiator;
			}
			
			_isDragging = true;
			_dragInitiator = dragInitiator;
			_sourceData = sourceData;
			_dragImage = dragImage;
			_dragListener = dragListener;
			if(_dragListener != null){
				addDragListener(_dragListener);
			}
			if(runningMotion){
				runningMotion.forceStop();
				runningMotion = null;
			}
			var container:DisplayObjectContainer = stage;
			if(dragProxyMC == null){
				dragProxyMC = new Sprite();
				dragProxyMC.touchable = false;
				dragProxyMC.name = "drag_image";
			}else{
				if(dragProxyMC.parent != null){
					dragProxyMC.parent.removeChild(dragProxyMC);
				}
			}
			if(dragProxyMC.numChildren > 0){
				dragProxyMC.removeChildAt(0);
			}
			container.addChild(dragProxyMC);
			
			dragProxyMC.addChild(dragImage.getDisplay());
			
			var touch:Touch=event.getTouch(stage);
			mouseOffset=touch.getLocation(dragInitiator);
			movingProxy=true;
			
			var dragEvent:DragAndDropEvent=getDragEventInstance(DragAndDropEvent.DRAG_START,_dragInitiator, _sourceData,null);
			_dispatchListenList(dragEvent);
			
			target = null;
			//initial image
			_dragImage.switchToRejectImage();
			stage.addEventListener(TouchEvent.TOUCH, stageTouchHandle);
		}
		private static function stageTouchHandle(e:TouchEvent):void{
			var touch:Touch=e.getTouch(curStage);
			switch(touch.phase){
				case TouchPhase.MOVED :
					onMouseMove(e);
					
					if(movingProxy){
						dragProxyMC.x=touch.globalX-mouseOffset.x;
						dragProxyMC.y=touch.globalY-mouseOffset.y;
					}
					break;
				case TouchPhase.BEGAN :
					//onMouseDown(e);
					break;
				case TouchPhase.ENDED : 
					onMouseUp(e);
					break;
			}
		}
		
		private static var movingProxy:Boolean=false;
		
		
		/**
		 * 是否正在拖动过程中;
		 * @return
		 *
		 */		
		public static function get isDragging():Boolean{
			return _isDragging;
		}
		
		/**
		 * Adds a drag listener to listener list.
		 * @param lis the listener to be add
		 */
		public static function addDragListener(lis:IDragListener):void{
			listeners.push(lis);
		}
		
		/**
		 * Removes the specified listener from listener list.
		 * @param lis the listener to be removed
		 */
		public static function removeDragListener(lis:IDragListener):void{
			
			var index:int=listeners.indexOf(lis);
			if(index==-1)return;
			
			listeners.splice(index,1);
		}
		
		/**
		 * Sets the motion of drag movie clip when a drop acted.
		 * <p>
		 * Generally if you want to do a custom motion of the dragging movie clip when dropped, you may
		 * call this method in the listener's <code>onDragDrop()</code> method.
		 * </p>
		 * <p>
		 * so you need to set to yours every drop time if you want.
		 * </p>
		 * @param motion the motion
		 * @see org.aswing.dnd.DropMotion
		 * @see org.aswing.dnd.RejectedMotion
		 * @see org.aswing.dnd.DragListener#onDragDrop()
		 */
		public static function setDropMotion(motion:IDropMotion):void{
			if(motion == null) motion = DEFAULT_DROP_MOTION;
			dropMotion = motion;
		}
		
		/**
		 * Returns the drag image.
		 */
		public static function getCurrentDragImage():IDraggingImage{
			return _dragImage;
		}
		
		/**
		 * Returns current drop target of dragging components by startDrag method.
		 * @return the drop target
		 * @see #startDrag()
		 * @see #getDropTarget()
		 */
		public static function getCurrentDropTarget():DisplayObject{
			return getDropTarget(curStage);
		}
		
		/**
		 * Returns current drop target component of specified position.
		 * @param pos the global point
		 * @return the drop target component
		 * @see #startDrag()
		 * @see #getDropTargetComponent()
		 */
		public static function getDropTargetComponent(pos:Point=null):AbstractDragger{
			return getDropTarget(curStage, pos, AbstractDragger,_dropTargetCheck) as AbstractDragger;
		}
		
		
		private static function _dropTargetCheck(tar:AbstractDragger):Boolean{
			return tar.dropTrigger;
		}
		
		/**
		 * Returns the drop target of specified position and specified class type.
		 * For example:<br/>
		 * <pre>
		 * getDropTarget(new Point(0, 0), TextField);
		 * will return the first textfield insance under the point, or null if not found.
		 *
		 * getDropTarget(null, null);
		 * will return the first display object insance under the current mouse point, or null if not found.
		 * </pre>
		 * @param stage the stage where the drop target should be in
		 * @param pos The point under which to look, in the coordinate space of the Stage.
		 * @param targetType the class type of the target, default is null, means any display object.
		 * @param addtionCheck, a check function, only return the target when function(target:DisplayOject) return true.
		 * default is null, means no this check.
		 * @return drop target
		 */
		public static function getDropTarget(stage:Stage, pos:Point=null, 
											 targetType:Class=null, 
											 addtionCheck:Function=null):DisplayObject{
			if(stage == null){
				return null;
			}
			if(pos == null){
				var touch:Touch=_lastMouseEvent.getTouch(stage);
				pos = new Point(touch.globalX, touch.globalY);
			}
			if(targetType == null){
				targetType = DisplayObject;
			}
			
			var targets:Vector.<DisplayObject> =StarlingHelper.getObjectsUnderPoint(stage,pos,targetType);
			var n:int = targets.length;
			for(var i:int=n-1; i>=0; i--){
				var tar:DisplayObject = targets[i];
				if(addtionCheck == null){
					return tar;
				}
				if(addtionCheck(tar)){
					return tar;
				}
				
			}
			return null;
		}
		
		private static function onMouseMove(event:TouchEvent):void{
			_lastMouseEvent=event;
			var newTarget:AbstractDragger =getDropTargetComponent();
			var dragEvent:DragAndDropEvent;
			action=MOVE;
			if(newTarget != target){
				if(target != null){
					_dragImage.switchToRejectImage();
					dragEvent=getDragEventInstance(DragAndDropEvent.DRAG_EXIT,_dragInitiator, _sourceData,event);
					_dispatchDragEvent(target,dragEvent);
					_dispatchListenList(dragEvent);
					target=null;
				}
				if(newTarget != null){
					dragEvent=getDragEventInstance(DragAndDropEvent.DRAG_ENTER,_dragInitiator, _sourceData,event);
					_dispatchDragEvent(newTarget,dragEvent);
					if(target){
						_dragImage.switchToAcceptImage();
					}
					
					_dispatchListenList(dragEvent);
				}
			}else{
				if(target != null){
					dragEvent=getDragEventInstance(DragAndDropEvent.DRAG_OVERRING,_dragInitiator, _sourceData,event);
					_dispatchDragEvent(target,dragEvent);
					_dispatchListenList(dragEvent);
				}
			}
			
			if(target==null){
				action=NONE;
			}
			_dragImage.action=action;
		}
		
		public static function acceptDragDrop(value:AbstractDragger):void{
			target=value;
		}
		
		private static function onMouseUp(e:TouchEvent):void{
			drop(e);
		}
		
		private static function onMouseDown(e:TouchEvent):void{
			drop(e); 
		}
		
		private static function drop(event:TouchEvent):void{
			movingProxy=false;
			
			var stage:Stage = curStage;
			stage.removeEventListener(TouchEvent.TOUCH,stageTouchHandle);
			_isDragging = false;
			
			if(target != null){
				setDropMotion(DEFAULT_DROP_MOTION);
			}else{
				setDropMotion(DEFAULT_REJECT_DROP_MOTION);
			}
			
			var dragEvent:DragAndDropEvent=getDragEventInstance(DragAndDropEvent.DRAG_DROP,_dragInitiator, _sourceData,event);
			if(target != null){
				_dispatchDragEvent(target,dragEvent);
			}
			_dispatchListenList(dragEvent);
			
			runningMotion = dropMotion;
			runningMotion.startMotion(_dragInitiator, dragProxyMC);
			
			if(_dragListener != null){
				removeDragListener(_dragListener);
			}
			curStage = null;
			_dragImage = null;
			_dragListener = null;
			_sourceData = null;
			target = null;
		}
		
		private static function getDragEventInstance(type:String,dragInitiator:AbstractDragger, sourceData:*,event:TouchEvent):DragAndDropEvent{
			
			var e:DragAndDropEvent;
			if(event){
				e= new DragAndDropEvent(type,
					dragInitiator,target,
					sourceData,event
				);
			}else{
				e= new DragAndDropEvent(type,
					dragInitiator,target,
					sourceData,null
				);
			}
			
			return e;
		}
		
		private static function _dispatchDragEvent(target:AbstractDragger,event:DragAndDropEvent):void{
			if(target.hasEventListener(event.type)){
				target.dispatchEvent(event);
			}
		}
		
		private static function _dispatchListenList(event:DragAndDropEvent):void{
			if(listeners.length==0)return;
			var lis:IDragListener;
			for each(lis in listeners){
				lis.onDragAndDropEvent(event);
			}
		}
	}
}

